﻿using Common;
using Lucene.Net.Analysis.En;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
using LuceneDemo.Extension;
using LuceneDemo.Interface;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;

namespace LuceneDemo.Service
{
    public class KeywordLuceneService : IKeywordLuceneService
    {
        public KeywordLuceneService()
        {

        }

        public void IndexAddDocuments(TrainInput input)
        {
            if(input == null || input.Bot == null)
            {
                return;
            }

            RemoveIndex(input.Bot);
            using (IndexWriter writer = IndexWriter(input.Bot, Index(input.Bot)))
            {
                foreach (var intent in input.Intents)
                {
                    foreach (var question in intent.Question)
                    {
                        Lucene.Net.Documents.Document document = IntentQuestionMapDocument(intent, question);
                        writer.AddDocument(document);
                    }
                }
                writer.Commit();
            }
        }

        public IEnumerable<IntentScore> Search(GetIntentByQuestionInput input)
        {
            Lucene.Net.Store.Directory index = Index(input.Bot);
            IndexAddQuestion(input.Bot, index, input.Question);
            Lucene.Net.Search.IndexSearcher searcher = IndexSearcher(index);
            var hits = ScoreDocs(index, searcher, input.Question);
            var result = ParseIntentScoreCollection(searcher, hits);
            var enrich = EnrichIntentScores(result);
            return enrich;
        }


        private string Path(BotType bot)
        {
            var baseDir = "index";
            var path = $"{baseDir}/{bot.SiteId}/{bot.Id}";
            return path;
        }

        private Lucene.Net.Store.Directory Index(BotType bot)
        {
            var key = Path(bot);
            var index = Utils.GetMemoryCache(key, () => new RAMDirectory(), 0);
            return index;
        }

        private void RemoveIndex(BotType bot)
        {
            var key = Path(bot);
            Utils.RemoveMemoryCache(key);
        }

        private IndexWriter IndexWriter(BotType bot, Lucene.Net.Store.Directory index)
        {
            var stopWords = EnglishAnalyzer.DefaultStopSet;
            var analyzer = new EnglishAnalyzer(Lucene.Net.Util.LuceneVersion.LUCENE_48, stopWords);
            var config = new IndexWriterConfig(Lucene.Net.Util.LuceneVersion.LUCENE_48, analyzer);
            IndexWriter writer = new IndexWriter(index, config);
            return writer;
        }
        
        private Lucene.Net.Documents.Document IntentQuestionMapDocument(BotIntentType intent, BotIntentQuestionType question, bool isStore = true)
        {
            Lucene.Net.Documents.Document document = new Lucene.Net.Documents.Document();
            document.Add(new StringField("intentId", intent.Id.ToString(), Field.Store.YES));
            document.Add(new StringField("intentName", intent.IntentName, Field.Store.YES));
            if (isStore)
            {
                document.Add(new TextField("question", new StringReader(question.Content)));
            }
            else
            {
                document.Add(new TextField("question", question.Content, Field.Store.NO));
            }
            return document;
        }
        
        private Lucene.Net.Search.IndexSearcher IndexSearcher(Lucene.Net.Store.Directory index)
        {
            IndexReader reader = DirectoryReader.Open(index);
            Lucene.Net.Search.IndexSearcher searcher = new Lucene.Net.Search.IndexSearcher(reader);
            return searcher;
        }
        
        private void IndexAddQuestion(BotType bot, Lucene.Net.Store.Directory index, string question)
        {
            using (IndexWriter writer = IndexWriter(bot, index))
            {
                var intent = new BotIntentType
                {
                    Id = Guid.Empty,
                    IntentName = string.Empty,

                };
                var botIntentQuestionType = new BotIntentQuestionType
                {
                    Content = question,
                };
                Lucene.Net.Documents.Document document = IntentQuestionMapDocument(intent, botIntentQuestionType);
                writer.UpdateDocument(new Term("intentId", Guid.Empty.ToString()), document);
                writer.Commit();
            }
        }
        
        private IEnumerable<ScoreDoc> ScoreDocs(Lucene.Net.Store.Directory index, IndexSearcher searcher, string question)
        {
            var stopWords = EnglishAnalyzer.DefaultStopSet;
            var analyzer = new EnglishAnalyzer(Lucene.Net.Util.LuceneVersion.LUCENE_48, stopWords);
            QueryBuilder builder = new QueryBuilder(analyzer);
            Query query = builder.CreateBooleanQuery("question", question);

            var collector = TopScoreDocCollector.Create(100, true);
            searcher.Search(query, collector);
            var hits = collector.GetTopDocs().ScoreDocs;
            return hits;
        }
       
        private IEnumerable<IntentScore> ParseIntentScoreCollection(IndexSearcher searcher, IEnumerable<ScoreDoc> scoreDocs)
        {
            if (scoreDocs != null)
            {
                IList<IntentScore> result = new List<IntentScore>();
                foreach (var hit in scoreDocs)
                {
                    var document = searcher.Doc(hit.Doc);
                    result.Add(new IntentScore
                    {
                        Id = Guid.Parse(document.Get("intentId")),
                        Name = document.Get("intentName"),
                        Score = hit.Score,
                    });
                }
                return result;
            }

            return null;
        }
       
        private IEnumerable<IntentScore> EnrichIntentScores(IEnumerable<IntentScore> intentScores)
        {
            if (intentScores != null)
            {
                return intentScores
                    .ResetScore(MaxScore(intentScores))
                    .Where(a => a.Id != Guid.Empty && a.Score > 0)
                    .GroupBy(a => a.Id)
                    .Select(a => a.OrderByDescending(a => a.Score).FirstOrDefault());
            }

            return null;
        }
       
        private float MaxScore(IEnumerable<IntentScore> intentScores)
        {
            return intentScores.FirstOrDefault(f => f.Id == Guid.Empty).Score;
        }
    }
}
